/* Copyright 2017, 2018, 2019, 2020 Gabriel Czernikier
 *
 * This file is part of Côtehaus.
 * Côtehaus is free software: you can redistribute it and/or modify
 * it under the terms of one of the following:
 * 1. The same dual license as that of Côtehaus itself, of which individual licenses are
 * the MIT License and the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 2. The MIT License.
 * 3. The GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * Côtehaus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * MIT License and the GNU General Public License for more details.
 * You should have received a copy of the MIT License and the GNU General Public License
 * along with Côtehaus.  If not, see <https://opensource.org/licenses/MIT> and <http://www.gnu.org/licenses/>.
 */
#define _BSD_SOURCE
#include <endian.h>
#include <errno.h>
#include <iconv.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "lgwseq.h"
#include "types.h"

lgwseq_t *for_lgwseq(size_t lg, const wchar_t *from) {
	lgwseq_t *s = malloc(sizeof(lgwseq_t));
	s->data = malloc(lg*sizeof(wchar_t));
	wmemcpy(s->data, from, lg);
	s->length = lg;
	return s;
}

size_t pack_string_data(const lgwseq_t *for_pack, void **packed_out) {
	iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
	iconv_t cdutf16 = iconv_open("UTF-16BE", "WCHAR_T");
	char *ibuf = (char *)for_pack->data;
	wchar_t *dataptr;
	wchar_t *endptr = for_pack->data+for_pack->length;
	size_t bufsize = (size_t)((char *)endptr-(char *)for_pack->data);
	size_t obuflen=bufsize;
	char *outbuf = malloc(bufsize), *obuf;
	// Packs the size attribute. The length of uleb128 suits in for packing numbers of up to 32-bit. Larger numbers are supported by the uleb* encoding family but will not be required by any instance of data encoded here.
	uint8_t uleb128[5] = {0x00,0x00,0x00,0x00,0x00};
	int span = 1;
	uint32_t length = for_pack->length;
	int i;
	for(i=0; i!=5; i++) {
		uleb128[i] = (length  >> (i*7)) & 0x7F;
		if(uleb128[i]!=0x00)
			span = i+1;
	}
	for(i=0; i!=span-1; i++)
		uleb128[i] |= 0x80;
	memcpy(outbuf, &uleb128, span);
	obuf = outbuf+span;

	do { // string recomposition loop
		// Get largest segment of straightfoward-representable codepoints (in opposition to surrogatable codepoints)
		dataptr = (wchar_t *)ibuf;
		/* The reason for why a straightfoward formula like dataptr<L'\U00010000' is not in use, steems in a lack of guaranty about the "arithmetic" behaviour of character sets to wide characters.
		 * The fear about this lack of guaranty can be reasoned from the following lines taken from GNU C Library Manual Reference
		 * http://www.gnu.org/software/libc/manual/html_mono/libc.html#Converting-a-Character (eitherly found at gnu-doc-reference package from Debian Repos)
		 * Why is it necessary to use such a complicated implementation and not simply cast '0' + val % 10 to a wide character?
		 * The answer is that there is no guarantee that one can perform this kind of arithmetic on the character of the character set used for wchar_t representation.
		 * In other situations the bytes are not constant at compile time and so the compiler cannot do the work. In situations like this, using btowc is required. 
		 */
		while(dataptr!=endptr && *dataptr!=L'\0'&& wmemcmp(dataptr, L"\U00010000",1)<0)
			dataptr++;
		size_t ibuflen = (char *)dataptr-ibuf;
		do {   // iconv loop
			if(iconv(cd, &ibuf, &ibuflen, &obuf, &obuflen) != (size_t) -1)
				break;
			else if(errno==E2BIG) {  // The buffer is full, make it larger.
				bufsize *= 2;
				ptrdiff_t obufoffset = obuf-outbuf;
				outbuf = realloc(outbuf, bufsize);
				obuf = outbuf+obufoffset;
				obuflen = bufsize-obufoffset;
			}
			else {
				char *str_err = strerror(errno);
				fprintf(stderr, "In function %s, %s reported  from iconv().", __func__, str_err);
				exit(EXIT_FAILURE);
			}
		}while(1);
		if(dataptr==endptr)
			break;
		// Surrogating...
		if(*dataptr==L'\0') {
			if(obuflen<2) {
				bufsize *= 2;
				ptrdiff_t obufoffset = obuf-outbuf;
				outbuf = realloc(outbuf, bufsize);
				obuf = outbuf+obufoffset;
				obuflen = bufsize-obufoffset;
			}
			const uint16_t NULLSURRG = 0xC080;
			memcpy(obuf, &NULLSURRG, 2);
			// Update buffer trackers
			dataptr++;
			ibuf = (char *)dataptr;
			obuf += 2;
			obuflen -= 2;
		}
		else {
			ibuflen = sizeof(wchar_t);
			uint32_t tmpbuf, *tmpbufptr=&tmpbuf;
			size_t tmpbuflen = (32/CHAR_BIT) + 32%CHAR_BIT?1:0;
			
			if(iconv(cdutf16, (char **)&dataptr, &ibuflen, (char **)&tmpbufptr, &tmpbuflen) == (size_t) -1) {
				char *str_err = strerror(errno);
				fprintf(stderr, "In function %s, %s reported from iconv().", __func__, str_err);
				exit(EXIT_FAILURE);
			}
			// Update buffer trackers
			ibuf = (char *)dataptr;
			// Apply bit pattern 110xxxxx 10xxxxxx 10xxxxxx twice, one on each UTF-16 surrogate half codeunit.
			uint32_t halves[2];
			halves[0] = 0xE08080 | ( (tmpbuf & 0xF0000000) >> 12) | ( (tmpbuf & 0x0FC00000) >> 18 ) | ( (tmpbuf & 0x003F0000) >> 24 );
			halves[1] = 0xE08080 | ( (tmpbuf & 0xF000) << 4) | ( (tmpbuf & 0x0FC0) >> 2 ) | ( (tmpbuf & 0x003F) >> 8 );
			// Check buffer limit and write the converted bytes
			if(obuflen<6) {
				bufsize *= 2;
				ptrdiff_t obufoffset = obuf-outbuf;
				outbuf = realloc(outbuf, bufsize);
				obuf = outbuf+obufoffset;
				obuflen = bufsize-obufoffset;
			}
			memcpy(obuf, (char *)halves+1, 3);   // First half. The first byte of the uint32_t is skypped as the 3 low-order bytes have the data.
			memcpy(obuf+3, (char *)(halves+1)+1, 3);   // Second half
			// Update buffer trackers
			obuf += 6;
			obuflen -= 6;
		}
	}while(dataptr!=endptr);
	// Packed string_data_item returning...
	ptrdiff_t offset = obuf-outbuf;
	*packed_out = realloc(outbuf, offset+1); // Reserves +1 position for the final '\0' appended right next
	*((char *)*packed_out+offset)='\0';
	return offset+1;
	/* With wchar_t strings, which length in term of chars can be derived by 
	 * simple distance through L'\0' (by means of wcschr function, for example), it deserves to be traded off with the 
	 * formula involving multiplication of wchar_t quantity per its unit size. And for the latter case, a certainty 
	 * about the constantness of wchar_t's size is needed to validate the formula's correctness.
	 * The nuances about all this are turned out with the fact that within sizeof(wchar_t)==n, n cannot be overridden by for example
	 * sizeof((wchar_t)L'a'), sizeof((wchar_t)L'\u0580'), sizeof((wchar_t)L'\u0800') or sizeof((wchar_t)L'\U00010000'), despite the fact that a similar function on a variable length encoding like UTF-8, did the function exist, would yield all different sizes for results.
	 */
}

int lgwseq_t_compar(const lgwseq_t *lgwseq1, const lgwseq_t *lgwseq2) {
	int cmp = wmemcmp(lgwseq1->data, lgwseq2->data, lgwseq1->length<lgwseq2->length?lgwseq1->length:lgwseq2->length);
	return cmp==0?lgwseq1->length-lgwseq2->length:cmp;
}

lgwseq_t *lgwseq_concat(int n_str, ...) {
	va_list ap;
	va_start(ap, n_str);
	int Length = 0;
	for(int str_no=0; str_no < n_str; str_no++)
		Length += va_arg(ap, lgwseq_t *)->length;
	va_end(ap);

	wchar_t *dst  = malloc(Length*sizeof(wchar_t));

	va_start(ap, n_str); 
	int base=0;
	for(int str_no=0; str_no<n_str; str_no++)
	{
		lgwseq_t *str = va_arg(ap, lgwseq_t *);
		wmemcpy(dst+base, str->data, str->length);
		base += str->length;
	}
	va_end(ap);
	lgwseq_t *cat = malloc(sizeof(lgwseq_t));
	cat-> data = dst;
	cat-> length = base;
	return cat;
}


